#include <iostream>

#include <atomic>
#include <memory>


template<typename T>
class DefaultDeleter
{
public:
    void operator()(T* ptr)
    {
        delete ptr;
    }
};

class BaseRefCounter
{
public:
    virtual ~BaseRefCounter() = default;

    virtual void destroy() = 0;

    std::atomic<int> refCount{};
    std::atomic<int> weakCount{};
};

template<typename T, typename Deleter = DefaultDeleter<T>>
class RefCounter : public BaseRefCounter
{
public:
    RefCounter() = default;
    explicit RefCounter(T* ptr, const Deleter& deleter = Deleter()) : ptr(ptr), deleter(deleter)
    {
        refCount = 1;
    }

    void destroy() override
    {
        deleter(ptr);
        ptr = nullptr;
    }

    T* ptr{};
    Deleter deleter{};
};


template<typename T>
class SharedPtr
{
public:
    SharedPtr() = default;
    explicit SharedPtr(T* ptr) : ptr(ptr),
        refCounter(new RefCounter<T>(ptr))
    {
    }

    template<typename Deleter>
    SharedPtr(T* ptr, const Deleter& D) : ptr(ptr),
        refCounter(new RefCounter<T, Deleter>(ptr, D))
    {
    }

    ~SharedPtr()
    {
        releaseRef();
    }

    SharedPtr(const SharedPtr& other) : ptr(other.ptr),
        refCounter(other.refCounter)
    {
        addRef();
    }

    SharedPtr& operator=(const SharedPtr& other)
    {
        if (this != &other)
        {
            this->reset();
            this->ptr = other.ptr;
            this->refCounter = other.refCounter;
            addRef();
        }
        return *this;
    }

    SharedPtr(SharedPtr&& other)  noexcept : ptr(other.ptr),
        refCounter(other.refCounter)
    {
        other.ptr = nullptr;
        other.refCounter = nullptr;
    }

    SharedPtr& operator=(SharedPtr&& other) noexcept
    {
        if (this != &other)
        {
            this->reset();
            this->ptr = other.ptr;
            this->refCounter = other.refCounter;
            other.ptr = nullptr;
            other.refCounter = nullptr;
        }

        return *this;
    }

    void swap(SharedPtr& other) noexcept
    {
        std::swap(ptr, other.ptr);
        std::swap(refCounter, other.refCounter);
    }

    bool operator==(const SharedPtr& other) const
    {
        return ptr == other.ptr;
    }

    bool operator==(std::nullptr_t) const
    {
        return ptr == nullptr;
    }

    bool operator!=(const SharedPtr& other) const
    {
        return ptr != other.ptr;
    }

    bool operator!=(std::nullptr_t) const
    {
        return ptr != nullptr;
    }
    void reset()
    {
        releaseRef();
        this->ptr = nullptr;
        this->refCounter = nullptr;
    }

    [[nodiscard]] int getRefCount() const
    {
        return refCounter->refCount;
    }

    [[nodiscard]] T* get() const
    {
        return ptr;
    }

    T* operator->() const
    {
        return ptr;
    }

    T& operator*() const
    {
        return *ptr;
    }

private:

    void addRef()
    {
        if (refCounter)
        {
            ++refCounter->refCount;
        }
    }

    void releaseRef()
    {
        if (refCounter)
        {
            --refCounter->refCount;
            if (refCounter->refCount == 0)
            {
                refCounter->destroy();
                delete refCounter;
                this->ptr = nullptr;
                this->refCounter = nullptr;
            }
        }
    }

    T* ptr{};
    BaseRefCounter* refCounter{};
};


class A
{
public:
    A()
    {
        std::cout << "construct A(), " << this->n << std::endl;
    }

    explicit A(int n) : n(n)
    {
        std::cout << "construct A(), " << this->n << std::endl;
    }

    ~A()
    {
        std::cout << "destruct ~A()" << std::endl;
    }

    int n = 10;
};


int main()
{
    SharedPtr<A> p2;

    std::cout << "------------Begin-----------" << std::endl;
    {
        SharedPtr<A> p1(new A(5));
        p2 = p1;
        SharedPtr<A> p3(std::move(p1));
        std::cout << "p2 = " << p2.get() << ", p1 =" << p1.get() << ", ref count: " << p2.getRefCount()  << std::endl;
        std::cout << (p1 == p2) << std::endl;
    }
    std::cout << "-------------end-----------" << std::endl;

    p2.reset();

    std::cout << "\n" << std::endl;
    {
        auto deleter = [](A* a) {
            std::cout << "deleter : " << a->n << std::endl;
            delete a;
        };
        SharedPtr<A> p1(new A(9), deleter);
    }

    return 0;
}


